/*
 * Copyright (c) 2008 Benoit Bouchez, Nicolas Giard
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.noocodecommit.wicket.stuff.jquery.plugin;

import java.io.Serializable;
import java.util.ArrayList;

import org.apache.wicket.ResourceReference;
import org.apache.wicket.behavior.HeaderContributor;
import org.apache.wicket.markup.html.PackageResource;
import org.apache.wicket.markup.html.WebPage;

import com.noocodecommit.wicket.stuff.jquery.core.JQSelector;
import com.noocodecommit.wicket.stuff.jquery.exception.JQJavascriptResourceNotFoundException;

/**
 * Class used to declare a JQuery plugin.<br>
 * <i>Remember to bind plugin with the JQCore instance.<br>
 * If you don't, .js and .css resources won't be included into your webpage.</i>
 * 
 * @author Benoit Bouchez
 * 
 */
public abstract class JQPlugin implements Serializable
{
	/** Plugin each widget init mode */
	public static int PLUGIN_INIT_EACH_WIDGET = 0;
	/** Plugin once init mode (needs selector setting) */
	public static int PLUGIN_INIT_ONCE = 1;

	/** List of javascript resources to use */
	private ArrayList<HeaderContributor> javascriptResources;
	/** List of css resources to use */
	private ArrayList<HeaderContributor> cssResources;
	/** List of plugin dependencies */
	private ArrayList<JQPlugin> dependencies;
	/** List of lang resources */
	private ArrayList<HeaderContributor> langResources;

	/** Linked Components */
	private ArrayList<IJQPluginComponent> attachedComponentsForDocumentReady;
	/** Linked Components supporting Ajax Behaviors */
	private ArrayList<IJQPluginComponent> attachedComponentsForDocumentReadySupportingAjax;
	/** Plugin key */
	private String key;

	/** Plugin init mode */
	private int initMode = PLUGIN_INIT_EACH_WIDGET;
	/** Plugin Selector for PLUGIN_INIT_ONCE mode */
	private String initOnceSelector = null;

	/**
	 * Constructor
	 * 
	 * @param key
	 *            : plugin identifier for JQuery binding
	 */
	public JQPlugin(String key)
	{
		super();
		this.key = key;
		this.javascriptResources = new ArrayList<HeaderContributor>();
		this.cssResources = new ArrayList<HeaderContributor>();
		this.dependencies = new ArrayList<JQPlugin>();
		this.langResources = new ArrayList<HeaderContributor>();
		this.attachedComponentsForDocumentReady = new ArrayList<IJQPluginComponent>();
		this.attachedComponentsForDocumentReadySupportingAjax = new ArrayList<IJQPluginComponent>();
	}

	/**
	 * Constructor
	 * 
	 * @param key
	 *            : plugin identifier for JQuery binding
	 * @param initMode
	 *            : plugin init mode
	 * @param initOnceSelector
	 *            : plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 */
	public JQPlugin(String key, int initMode, String initOnceSelector)
	{
		super();
		this.key = key;
		this.initMode = initMode;
		this.initOnceSelector = initOnceSelector;
		this.javascriptResources = new ArrayList<HeaderContributor>();
		this.cssResources = new ArrayList<HeaderContributor>();
		this.dependencies = new ArrayList<JQPlugin>();
		this.langResources = new ArrayList<HeaderContributor>();
		this.attachedComponentsForDocumentReady = new ArrayList<IJQPluginComponent>();
		this.attachedComponentsForDocumentReadySupportingAjax = new ArrayList<IJQPluginComponent>();
	}

	/**
	 * Constructor to use for plugins with one single javascript file to include
	 * 
	 * @param key
	 *            : plugin identifier for JQuery binding
	 * @param initMode
	 *            : plugin init mode
	 * @param initOnceSelector
	 *            : plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * @param pathForContributor
	 *            Path for the javascript file to include (relative to <code>scope</code>
	 * @param scope
	 *            Class scope
	 */
	public JQPlugin(String key, int initMode, String initOnceSelector, String pathForContributor, Class<?> scope)
	{
		this(key, initMode, initOnceSelector);
		this.javascriptResources = new ArrayList<HeaderContributor>();
		HeaderContributor contributor = HeaderContributor.forJavaScript(scope, pathForContributor);
		this.javascriptResources.add(contributor);
		this.cssResources = new ArrayList<HeaderContributor>();
	}

	/**
	 * Constructor to use for plugins with multiple javascript files to include
	 * 
	 * @param key
	 *            : plugin identifier for JQuery binding
	 * @param initMode
	 *            : plugin init mode
	 * @param initOnceSelector
	 *            : plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * @param javascriptContributors
	 *            List of paths for the javascript files to include
	 */
	public JQPlugin(String key, int initMode, String initOnceSelector, ArrayList<HeaderContributor> javascriptContributors)
	{
		this(key, initMode, initOnceSelector);
		this.javascriptResources = javascriptContributors;
	}

	/**
	 * Constructor to use for plugins with multiple javascript and css files to include
	 * 
	 * @param key
	 *            : plugin identifier for JQuery binding
	 * @param initMode
	 *            : plugin init mode
	 * @param initOnceSelector
	 *            : plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * @param javascriptContributors
	 *            List of javascript files to include
	 * @param cssContributors
	 *            List of css files to include
	 */
	public JQPlugin(String key, int initMode, String initOnceSelector, ArrayList<HeaderContributor> javascriptContributors, ArrayList<HeaderContributor> cssContributors)
	{
		this(key, initMode, initOnceSelector);
		this.javascriptResources = javascriptContributors;
		this.cssResources = cssContributors;
	}

	/**
	 * Constructor to use for plugins with multiple javascript and css files to include and plugin dependencies
	 * 
	 * @param key
	 *            : plugin identifier for JQuery binding
	 * @param initMode
	 *            : plugin init mode
	 * @param initOnceSelector
	 *            : plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * @param javascriptContributors
	 *            List of javascript files to include
	 * @param cssContributors
	 *            List of css files to include
	 * @param dependencies
	 *            List of plugin dependencies
	 */
	public JQPlugin(String key, int initMode, String initOnceSelector, ArrayList<HeaderContributor> javascriptContributors, ArrayList<HeaderContributor> cssContributors, ArrayList<JQPlugin> dependencies)
	{
		this(key, initMode, initOnceSelector);
		this.javascriptResources = javascriptContributors;
		this.cssResources = cssContributors;
		this.dependencies = dependencies;
	}

	/**
	 * Gets all javascript and css resources in the collections and and register it in the page<br>
	 * Also register dependencies for the plugin
	 * 
	 * @param page
	 *            WebPage for registering resource
	 * @param scope
	 *            Class of the plugin
	 */
	public void initialize(WebPage page, Class<?> scope)
	{
		// Register dependencies first
		for (int i = 0; i < this.dependencies.size(); i++)
		{
			dependencies.get(i).initialize(page, scope);
		}
		// Register all javascript
		for (int i = 0; i < this.javascriptResources.size(); i++)
		{
			page.add(this.javascriptResources.get(i));
		}
		// Register all css resources
		for (int i = 0; i < this.cssResources.size(); i++)
		{
			page.add(this.cssResources.get(i));
		}
		// Register all i18n resources
		for (int i = 0; i < this.langResources.size(); i++)
		{
			page.add(this.langResources.get(i));
		}
	}

	/**
	 * Adds a plugin dependency
	 * 
	 * @param plugin
	 *            Plugin dependency
	 */
	public void addDependency(JQPlugin plugin)
	{
		this.dependencies.add(plugin);
	}

	/**
	 * @return the list of plugin dependencies
	 */
	public ArrayList<JQPlugin> getDependencies()
	{
		return dependencies;
	}

	/**
	 * @return the list of javascript resources
	 * @deprecated since 1.6 use addJavascriptResource instead
	 */
	public ArrayList<HeaderContributor> getJavascriptResources()
	{
		return javascriptResources;
	}

	/**
	 * Add a javascript resource to plugin.<br>
	 * Before adding the resource, the existence of resource path is checked.<br>
	 * If it fails, existence of resource failPath is checked.<br>
	 * If it fails again, a runtime exception is thrown.
	 * 
	 * @param scope
	 *            plugin class
	 * @param path
	 *            resource path
	 * @param failPath
	 *            alternative resource path
	 */
	public void addJavascriptResource(Class<?> scope, String path, String failPath)
	{
		if (this.checkJavascriptResourceExistence(scope, path))
		{
			this.javascriptResources.add(HeaderContributor.forJavaScript(scope, path));
		}
		else if (this.checkJavascriptResourceExistence(scope, failPath))
		{
			this.javascriptResources.add(HeaderContributor.forJavaScript(scope, failPath));
		}
		else
		{
			throw new JQJavascriptResourceNotFoundException(path + "or " + failPath, scope);
		}
	}

	/**
	 * Add a css resource to plugin.
	 * 
	 * @param path
	 *            resource path
	 */
	public void addCssResource(String path)
	{
		this.cssResources.add(HeaderContributor.forCss(path));
	}

	/**
	 * Add a css resource to plugin.
	 * 
	 * @param resourceReference
	 */
	public void addCssResource(ResourceReference resourceReference)
	{
		this.cssResources.add(HeaderContributor.forCss(resourceReference));
	}

	/**
	 * @return the list of lang resources
	 */
	public ArrayList<HeaderContributor> getLangResources()
	{
		return langResources;
	}

	/**
	 * Registers the specified component for use in $(document).ready function<br>
	 * Use it if the component with DOM id $('#id') have specific behaviour. If you want all components with a specific class name to have specific behavior, don't attach component but use a JQueryPluginSetup instead
	 * 
	 * @param component
	 */
	public void attachComponentForDocumentReady(IJQPluginComponent component)
	{
		this.attachedComponentsForDocumentReady.add(component);
	}

	/**
	 * @return the list of registered components for use in $(document).ready functio
	 */
	public ArrayList<IJQPluginComponent> getAttachedComponentsForDocumentReady()
	{
		return attachedComponentsForDocumentReady;
	}

	/**
	 * 
	 * @param component
	 * @return true if component was attached, false if it is already attached
	 */
	public boolean attachComponentForDocumentReadySupportingAjax(IJQPluginComponent component)
	{
		if (!this.attachedComponentsForDocumentReadySupportingAjax.contains(component))
		{
			this.attachedComponentsForDocumentReadySupportingAjax.add(component);
			return true;
		}
		return false;
	}

	/**
	 * @return the list of registered components for use in $(document).ready functio
	 */
	public ArrayList<IJQPluginComponent> getAttachedComponentsForDocumentReadySupportingAjax()
	{
		return attachedComponentsForDocumentReadySupportingAjax;
	}

	/**
	 * Gets the plugin key
	 * 
	 * @return plugin key
	 */
	public String getKey()
	{
		return key;
	}

	/**
	 * Gets plugin init mode
	 * 
	 * @return plugin init mode
	 */
	public int getInitMode()
	{
		return initMode;
	}

	/**
	 * Sets plugin init mode
	 * 
	 * @param initMode
	 *            plugin init mode
	 */
	public void setInitMode(int initMode)
	{
		this.initMode = initMode;
	}

	/**
	 * Gets plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * 
	 * @return JQuery selector
	 */
	public String getInitOnceSelector()
	{
		return initOnceSelector;
	}

	/**
	 * Sets plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * 
	 * @param initOnceSelector
	 *            JQuery selector
	 */
	public void setInitOnceSelector(String initOnceSelector)
	{
		this.initOnceSelector = initOnceSelector;
	}

	/**
	 * Sets plugin selector when initMode is PLUGIN_INIT_MODE_ONCE
	 * 
	 * @param initOnceSelector
	 *            JQuery selector
	 */
	public void setInitOnceSelector(JQSelector initOnceSelector)
	{
		this.initOnceSelector = initOnceSelector.build();
	}

	protected boolean checkJavascriptResourceExistence(Class<?> scope, String path)
	{
		PackageResource packageResource = PackageResource.get(scope, path, null, null);
		return (packageResource.getResourceStream(false) == null ? false : true);
	}

	/**
	 * @return this plugin document ready javascript call
	 */
	public abstract String getPluginReadyScript();
}
